/***************************************************************************
*                               MessagePump.cs
*                            -------------------
*   begin                : May 1, 2002
*   copyright            : (C) The RunUO Software Team
*   email                : info@runuo.com
*
*   $Id: MessagePump.cs 402 2009-10-17 07:28:17Z mark $
*
***************************************************************************/

/***************************************************************************
*
*   This program is free software; you can redistribute it and/or modify
*   it under the terms of the GNU General Public License as published by
*   the Free Software Foundation; either version 2 of the License, or
*   (at your option) any later version.
*
***************************************************************************/

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Server.Diagnostics;

namespace Server.Network
{
    public class MessagePump
    {
        private Listener[] m_Listeners;
        private Queue<NetState> m_Queue;
        private Queue<NetState> m_WorkingQueue;
        private readonly Queue<NetState> m_Throttled;
        private readonly byte[] m_Peek;

        public MessagePump()
        {
            IPEndPoint[] ipep = Listener.EndPoints;

            this.m_Listeners = new Listener[ipep.Length];

            bool success = false;

            do
            {
                for (int i = 0; i < ipep.Length; i++)
                {
                    Listener l = new Listener(ipep[i]);
                    if (!success && l != null)
                        success = true;
                    this.m_Listeners[i] = l;
                }

                if (!success)
                {
                    Console.WriteLine("Retrying...");
                    Thread.Sleep(10000);
                }
            }
            while (!success);

            this.m_Queue = new Queue<NetState>();
            this.m_WorkingQueue = new Queue<NetState>();
            this.m_Throttled = new Queue<NetState>();
            this.m_Peek = new byte[4];
        }

        public Listener[] Listeners
        {
            get
            {
                return this.m_Listeners;
            }
            set
            {
                this.m_Listeners = value;
            }
        }

        public void AddListener(Listener l)
        {
            Listener[] old = this.m_Listeners;

            this.m_Listeners = new Listener[old.Length + 1];

            for (int i = 0; i < old.Length; ++i)
                this.m_Listeners[i] = old[i];

            this.m_Listeners[old.Length] = l;
        }

        private void CheckListener()
        {
            for (int j = 0; j < this.m_Listeners.Length; ++j)
            {
                Socket[] accepted = this.m_Listeners[j].Slice();

                for (int i = 0; i < accepted.Length; ++i)
                {
                    NetState ns = new NetState(accepted[i], this);
                    ns.Start();

                    if (ns.Running)
                        Console.WriteLine("Client: {0}: Connected. [{1} Online]", ns, NetState.Instances.Count);
                }
            }
        }

        public void OnReceive(NetState ns)
        {
            lock (this)
                this.m_Queue.Enqueue(ns);

            Core.Set();
        }

        public void Slice()
        {
            this.CheckListener();

            lock (this)
            {
                Queue<NetState> temp = this.m_WorkingQueue;
                this.m_WorkingQueue = this.m_Queue;
                this.m_Queue = temp;
            }

            while (this.m_WorkingQueue.Count > 0)
            {
                NetState ns = this.m_WorkingQueue.Dequeue();

                if (ns.Running)
                    this.HandleReceive(ns);
            }

            lock (this)
            {
                while (this.m_Throttled.Count > 0)
                    this.m_Queue.Enqueue(this.m_Throttled.Dequeue());
            }
        }

        private const int BufferSize = 4096;
        private readonly BufferPool m_Buffers = new BufferPool("Processor", 4, BufferSize);

        public bool HandleReceive(NetState ns)
        {
            ByteQueue buffer = ns.Buffer;

            if (buffer == null || buffer.Length <= 0)
                return true;

            lock (buffer)
            {
                int length = buffer.Length;

                if (!ns.Seeded)
                {
                    if (buffer.GetPacketID() == 0xEF)
                    {
                        // new packet in client	6.0.5.0	replaces the traditional seed method with a	seed packet
                        // 0xEF	= 239 =	multicast IP, so this should never appear in a normal seed.	 So	this is	backwards compatible with older	clients.
                        ns.Seeded = true;
                    }
                    else if (buffer.Length >= 4)
                    {
                        buffer.Dequeue(this.m_Peek, 0, 4);

                        int seed = (this.m_Peek[0] << 24) | (this.m_Peek[1] << 16) | (this.m_Peek[2] << 8) | this.m_Peek[3];

                        if (seed == 0)
                        {
                            Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", ns);
                            ns.Dispose();
                            return false;
                        }

                        ns.m_Seed = seed;
                        ns.Seeded = true;

                        length = buffer.Length;
                    }
                    else
                    {
                        return true;
                    }
                }

                while (length > 0 && ns.Running)
                {
                    int packetID = buffer.GetPacketID();

                    if (!ns.SentFirstPacket && packetID != 0xF0 && packetID != 0xF1 && packetID != 0xCF && packetID != 0x80 && packetID != 0x91 && packetID != 0xA4 && packetID != 0xEF)
                    {
                        Console.WriteLine("Client: {0}: Encrypted client detected, disconnecting", ns);
                        ns.Dispose();
                        break;
                    }

                    PacketHandler handler = ns.GetHandler(packetID);

                    if (handler == null)
                    {
                        byte[] data = new byte[length];
                        length = buffer.Dequeue(data, 0, length);

                        new PacketReader(data, length, false).Trace(ns);

                        break;
                    }

                    int packetLength = handler.Length;

                    if (packetLength <= 0)
                    {
                        if (length >= 3)
                        {
                            packetLength = buffer.GetPacketLength();

                            if (packetLength < 3)
                            {
                                ns.Dispose();
                                break;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }

                    if (length >= packetLength)
                    {
                        if (handler.Ingame && ns.Mobile == null)
                        {
                            Console.WriteLine("Client: {0}: Sent ingame packet (0x{1:X2}) before having been attached to a mobile", ns, packetID);
                            ns.Dispose();
                            break;
                        }
                        else if (handler.Ingame && ns.Mobile.Deleted)
                        {
                            ns.Dispose();
                            break;
                        }
                        else
                        {
                            ThrottlePacketCallback throttler = handler.ThrottleCallback;

                            if (throttler != null && !throttler(ns))
                            {
                                this.m_Throttled.Enqueue(ns);
                                return false;
                            }

                            PacketReceiveProfile prof = PacketReceiveProfile.Acquire(packetID);

                            if (prof != null)
                            {
                                prof.Start();
                            }

                            byte[] packetBuffer;

                            if (BufferSize >= packetLength)
                                packetBuffer = this.m_Buffers.AcquireBuffer();
                            else
                                packetBuffer = new byte[packetLength];

                            packetLength = buffer.Dequeue(packetBuffer, 0, packetLength);

                            PacketReader r = new PacketReader(packetBuffer, packetLength, handler.Length != 0);

                            handler.OnReceive(ns, r);
                            length = buffer.Length;

                            if (BufferSize >= packetLength)
                                this.m_Buffers.ReleaseBuffer(packetBuffer);

                            if (prof != null)
                            {
                                prof.Finish(packetLength);
                            }
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }

            return true;
        }
    }
}